#include <queue>
#include <future>
#include "processor.hpp"

std::queue<std::thread> threads;
std::thread iot;
std::map<int, std::future<void>> futures;
int running = 0;

void Processor::newCore(int ID)
{
	MMUs[ID] = new MMU();
	CACHEs[ID] = new Cache();
	cores[ID] = new CalcCore(ID, *MMUs[ID], *CACHEs[ID]);
	cores[ID]->CalcCore::init(this->bus);
	futures[ID] = (std::async(std::launch::async, [this, ID]() {
		running++;
		while (cores[ID]->isRunning())
			(*cores[ID])();
		CACHEs[ID]->shutdown();
		running--; }));
}

void Processor::init(Atomic<IOBus *> &bus)
{
	MMUs[0] = new MMU();
	CACHEs[0] = new Cache();
	cores[0] = new CalcCore(0, *MMUs[0], *CACHEs[0]);
	cores[0]->CalcCore::init(bus);
	this->bus = bus;
	mountIO(0, SEIN, nullptr);
	mountIO(1, SEOUT, nullptr);
	mountIO(2, BLIO, "disk.bin");
}

void Processor::run()
{
	for (auto [id, core] : cores)
	{
		futures[id] = (std::async(std::launch::async, [this, core, id]()
								  {
										running++;
			while (core->isRunning())
				(*core)();
			CACHEs[id]->shutdown();
		running--; }));
	}
	iot = std::thread([this]() {
		while (running > 0)
		{
			while (bus.is_done());
			uint8_t option = bus.get_direction() ? 1 : 0;
			option = (bus.get_port() & 0x01) ? (option | 0x02) : option;
			(*this)(bus.get_data(), (bus.get_port() >> 1), option);
			bus.setup();
		} });
	iot.detach();
	// 等待所有异步任务完成
	for (auto &future : futures)
		future.second.get();
}

void Processor::mountIO(uint16_t id, const IOTYPE type, const char *path = nullptr)
{
	switch (type)
	{
	case IOTYPE::BLIN:
		io[id] = new FileInput(path);
		break;
	case IOTYPE::BLOUT:
		io[id] = new FileOutput(path);
		break;
	case IOTYPE::BLIO:
		io[id] = new FileIO(path);
		break;
	case IOTYPE::SEIN:
		io[id] = new SerialInput();
		break;
	case IOTYPE::SEOUT:
		io[id] = new SerialOutput();
		break;
	default:
		throw std::runtime_error("Invalid IO type");
		break;
	}
}

void Processor::operator()(uint64_t data, uint16_t port, uint8_t opt)
{
	switch (opt)
	{
	case 0: // control,write
		switch(io[port]->getType())
		{
		case IOTYPE::SEIN: // Serial input,close
			io[port]->setStatus(IOSTATUS(data));
			if (io[port]->getStatus() & (READY|READ))
				threads.push(std::thread(&SerialInput::in, static_cast<SerialInput *>(io[port])));
			break;
		case IOTYPE::SEOUT: // Serial output,close
			io[port]->setStatus(IOSTATUS(data));
			if (io[port]->getStatus() & (READY | WRITE))
				static_cast<SerialOutput *>(io[port])->out();
			break;
		case IOTYPE::BLIN: // File input,close
		case IOTYPE::BLOUT: // File output,close
		case IOTYPE::BLIO: // File io,close
			io[port]->setStatus(IOSTATUS(data));
			break;
		}
		break;
	case 1: // control,read
		bus.set_data(io[port]->getStatus(), 2);
		break;
	case 2: // data,write
		switch (io[port]->getType())
		{
		case IOTYPE::SEOUT: // Serial output,write a byte from reg
			static_cast<SerialOutput *>(io[port])->in(data & 0xff);
			break;
		case IOTYPE::BLIN: // File input
			switch (io[port]->getStatus())
			{
			case SET0:
			case SET1:
				static_cast<FileInput *>(io[port])->setAddr(data);
				break;
			case READ:
			case READY:
				threads.push(std::thread(&FileInput::read, static_cast<FileInput *>(io[port])));
				break;
			default:
				break;
			}
			break;
		case IOTYPE::BLOUT: // File output
			switch (io[port]->getStatus())
			{
			case SET0:
			case SET1:
				static_cast<FileOutput *>(io[port])->setAddr(data);
				break;
			default:
				break;
			}
			break;
		case IOTYPE::BLIO: // File io
			switch (io[port]->getStatus())
			{
			case SET0:
			case SET1:
				static_cast<FileIO *>(io[port])->setAddr(data);
				break;
			case READ:
			case READY:
				threads.push(std::thread(&FileIO::read, static_cast<FileIO *>(io[port])));
				break;
			default:
				break;
			}
			break;
		}
		break;
	case 3: // data,read
		switch (io[port]->getType())
		{
		case IOTYPE::SEIN: // Serial input,read a byte to reg
			bus.set_data(static_cast<SerialInput *>(io[port])->out());
			break;
		case IOTYPE::BLIN: // File input
			switch (io[port]->getStatus())
			{
			case GET0:
			case GET1:
				bus.set_data(static_cast<FileInput *>(io[port])->getAddr());
				break;
			default:
				break;
			}
			break;
		case IOTYPE::BLOUT: // File output
			switch (io[port]->getStatus())
			{
			case GET0:
			case GET1:
				bus.set_data(static_cast<FileOutput *>(io[port])->getAddr());
				break;
			case WRITE:
			case READY:
				threads.push(std::thread(&FileOutput::write, static_cast<FileOutput *>(io[port])));
				break;
			default:
				break;
			}
			break;
		case IOTYPE::BLIO: // File io
			switch (io[port]->getStatus())
			{
			case GET0:
			case GET1:
				bus.set_data(static_cast<FileIO *>(io[port])->getAddr());
				break;
			case WRITE:
			case READY:
				threads.push(std::thread(&FileIO::write, static_cast<FileIO *>(io[port])));
				break;
			default:
				break;
			}
			break;
		default:
			break;
		}
		if (!threads.empty())
		{
			threads.front().detach();
			threads.pop();
		}
	}
}